Skip to content

Commit

Permalink
fix(composer): Restore any modified files after analysis
Browse files Browse the repository at this point in the history
Introduce a `LockfileProvider` that ensures a lockfile to be present by
either using the existing one, or enabling the creation of one and
creating it. A self-created lockfile is deleted afterwards, and any
modifications to the definition file are reverted.

Signed-off-by: Sebastian Schuberth <[email protected]>
  • Loading branch information
sschuberth committed Dec 12, 2024
1 parent ac6b3ae commit a9ce535
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 30 deletions.
35 changes: 5 additions & 30 deletions plugins/package-managers/composer/src/main/kotlin/Composer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,8 @@ import org.ossreviewtoolkit.utils.ort.showStackTrace

import org.semver4j.RangesList
import org.semver4j.RangesListFactory
import org.semver4j.Semver

private const val COMPOSER_PHAR_BINARY = "composer.phar"
private const val COMPOSER_LOCK_FILE = "composer.lock"
private const val SCOPE_NAME_REQUIRE = "require"
private const val SCOPE_NAME_REQUIRE_DEV = "require-dev"
private val ALL_SCOPE_NAMES = setOf(SCOPE_NAME_REQUIRE, SCOPE_NAME_REQUIRE_DEV)
Expand Down Expand Up @@ -139,7 +137,11 @@ class Composer(
}

val lockfile = stashDirectories(workingDir.resolve("vendor")).use { _ ->
ensureLockfile(workingDir).let {
val lockfileProvider = LockfileProvider(definitionFile)

requireLockfile(workingDir) { lockfileProvider.lockfile.isFile }

lockfileProvider.ensureLockfile {
logger.info { "Parsing lockfile at '$it'..." }
parseLockfile(it.readText())
}
Expand Down Expand Up @@ -242,33 +244,6 @@ class Composer(
scopeDependencies = scopes
)
}

private fun ensureLockfile(workingDir: File): File {
val lockfile = workingDir.resolve(COMPOSER_LOCK_FILE)

val hasLockfile = lockfile.isFile
requireLockfile(workingDir) { hasLockfile }
if (hasLockfile) return lockfile

// Ensure that the build is not configured to disallow the creation of lockfiles.
ComposerCommand.run(workingDir, "--no-interaction", "config", "--unset", "lock").requireSuccess()

val composerVersion = Semver(ComposerCommand.getVersion(workingDir))
val args = buildList {
add("--no-interaction")
add("update")
add("--ignore-platform-reqs")

if (composerVersion.major >= 2) {
add("--no-install")
add("--no-audit")
}
}

ComposerCommand.run(workingDir, *args.toTypedArray()).requireSuccess()

return lockfile
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright (C) 2024 The ORT Project Authors (see <https://github.com/oss-review-toolkit/ort/blob/main/NOTICE>)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
* License-Filename: LICENSE
*/

package org.ossreviewtoolkit.plugins.packagemanagers.composer

import java.io.File

import kotlin.io.path.moveTo

import org.semver4j.Semver

private const val COMPOSER_LOCK_FILE = "composer.lock"

class LockfileProvider(private val definitionFile: File) {
private val workingDir = definitionFile.parentFile

val lockfile = workingDir.resolve(COMPOSER_LOCK_FILE)

fun <T> ensureLockfile(block: (File) -> T): T {
if (lockfile.isFile) return block(lockfile)

val definitionFileBackup = enableLockfileCreation()

return try {
require(createLockFile())
block(lockfile)
} finally {
lockfile.delete()
definitionFileBackup?.also { it.toPath().moveTo(definitionFile.toPath(), overwrite = true) }
}
}

private fun enableLockfileCreation(): File? {
var definitionFileBackup: File? = null
val lockConfig = ComposerCommand.run(workingDir, "--no-interaction", "config", "lock")

if (lockConfig.isSuccess && lockConfig.stdout.trim() == "false") {
File.createTempFile("composer", "json", workingDir).also {
// The above call already creates an empty file, so the copy call needs to overwrite it.
definitionFileBackup = definitionFile.copyTo(it, overwrite = true)
}

// Ensure that the build is not configured to disallow the creation of lockfiles.
val unsetLock = ComposerCommand.run(workingDir, "--no-interaction", "config", "--unset", "lock")
if (unsetLock.isError) {
definitionFileBackup?.delete()
return null
}
}

return definitionFileBackup
}

private fun createLockFile(): Boolean {
val args = buildList {
add("--no-interaction")
add("update")
add("--ignore-platform-reqs")

val composerVersion = Semver(ComposerCommand.getVersion(workingDir))
if (composerVersion.major >= 2) {
add("--no-install")
add("--no-audit")
}
}

val update = ComposerCommand.run(workingDir, *args.toTypedArray())
return update.isSuccess
}
}

0 comments on commit a9ce535

Please sign in to comment.