/* * Copyright 2014-present Facebook, Inc. * * 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 * * http://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. */ package com.facebook.buck.cxx; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.junit.Assume.assumeTrue; import com.facebook.buck.cli.FakeBuckConfig; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.rules.BuildRuleResolver; import com.facebook.buck.rules.DefaultTargetNodeToBuildRuleTransformer; import com.facebook.buck.rules.SourcePathResolver; import com.facebook.buck.rules.SourcePathRuleFinder; import com.facebook.buck.rules.TargetGraph; import com.facebook.buck.step.ExecutionContext; import com.facebook.buck.step.TestExecutionContext; import com.facebook.buck.step.fs.FileScrubberStep; import com.facebook.buck.testutil.TestConsole; import com.facebook.buck.testutil.integration.TemporaryPaths; import com.facebook.buck.util.environment.Platform; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import java.io.FileInputStream; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.Optional; import org.apache.commons.compress.archivers.ar.ArArchiveEntry; import org.apache.commons.compress.archivers.ar.ArArchiveInputStream; import org.hamcrest.Matchers; import org.junit.Rule; import org.junit.Test; public class ArchiveStepIntegrationTest { @Rule public TemporaryPaths tmp = new TemporaryPaths(); @Test @SuppressWarnings("PMD.AvoidUsingOctalValues") public void thatGeneratedArchivesAreDeterministic() throws IOException, InterruptedException { assumeTrue(Platform.detect() == Platform.MACOS || Platform.detect() == Platform.LINUX); ProjectFilesystem filesystem = new ProjectFilesystem(tmp.getRoot()); CxxPlatform platform = CxxPlatformUtils.build(new CxxBuckConfig(FakeBuckConfig.builder().build())); // Build up the paths to various files the archive step will use. SourcePathResolver sourcePathResolver = new SourcePathResolver( new SourcePathRuleFinder( new BuildRuleResolver( TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()))); Archiver archiver = platform.getAr(); Path output = filesystem.getPath("output.a"); Path input = filesystem.getPath("input.dat"); filesystem.writeContentsToPath("blah", input); Preconditions.checkState(filesystem.resolve(input).toFile().setExecutable(true)); // Build an archive step. ArchiveStep archiveStep = new ArchiveStep( filesystem, archiver.getEnvironment(sourcePathResolver), archiver.getCommandPrefix(sourcePathResolver), ImmutableList.of(), getArchiveOptions(false), output, ImmutableList.of(input), archiver); FileScrubberStep fileScrubberStep = new FileScrubberStep(filesystem, output, platform.getAr().getScrubbers()); // Execute the archive step and verify it ran successfully. ExecutionContext executionContext = TestExecutionContext.newInstance(); TestConsole console = (TestConsole) executionContext.getConsole(); int exitCode = archiveStep.execute(executionContext).getExitCode(); assertEquals("archive step failed: " + console.getTextWrittenToStdErr(), 0, exitCode); exitCode = fileScrubberStep.execute(executionContext).getExitCode(); assertEquals("archive scrub step failed: " + console.getTextWrittenToStdErr(), 0, exitCode); // Now read the archive entries and verify that the timestamp, UID, and GID fields are // zero'd out. try (ArArchiveInputStream stream = new ArArchiveInputStream(new FileInputStream(filesystem.resolve(output).toFile()))) { ArArchiveEntry entry = stream.getNextArEntry(); assertEquals( ObjectFileCommonModificationDate.COMMON_MODIFICATION_TIME_STAMP, entry.getLastModified()); assertEquals(0, entry.getUserId()); assertEquals(0, entry.getGroupId()); assertEquals(String.format("0%o", entry.getMode()), 0100644, entry.getMode()); } } @Test public void emptyArchives() throws IOException, InterruptedException { ProjectFilesystem filesystem = new ProjectFilesystem(tmp.getRoot()); CxxPlatform platform = CxxPlatformUtils.build(new CxxBuckConfig(FakeBuckConfig.builder().build())); // Build up the paths to various files the archive step will use. SourcePathResolver sourcePathResolver = new SourcePathResolver( new SourcePathRuleFinder( new BuildRuleResolver( TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()))); Archiver archiver = platform.getAr(); Path output = filesystem.getPath("output.a"); // Build an archive step. ArchiveStep archiveStep = new ArchiveStep( filesystem, archiver.getEnvironment(sourcePathResolver), archiver.getCommandPrefix(sourcePathResolver), ImmutableList.of(), getArchiveOptions(false), output, ImmutableList.of(), archiver); // Execute the archive step and verify it ran successfully. ExecutionContext executionContext = TestExecutionContext.newInstance(); TestConsole console = (TestConsole) executionContext.getConsole(); int exitCode = archiveStep.execute(executionContext).getExitCode(); assertEquals("archive step failed: " + console.getTextWrittenToStdErr(), 0, exitCode); // Now read the archive entries and verify that the timestamp, UID, and GID fields are // zero'd out. try (ArArchiveInputStream stream = new ArArchiveInputStream(new FileInputStream(filesystem.resolve(output).toFile()))) { assertThat(stream.getNextArEntry(), Matchers.nullValue()); } } @Test public void inputDirs() throws IOException, InterruptedException { assumeTrue(Platform.detect() == Platform.MACOS || Platform.detect() == Platform.LINUX); ProjectFilesystem filesystem = new ProjectFilesystem(tmp.getRoot()); CxxPlatform platform = CxxPlatformUtils.build(new CxxBuckConfig(FakeBuckConfig.builder().build())); // Build up the paths to various files the archive step will use. SourcePathResolver sourcePathResolver = new SourcePathResolver( new SourcePathRuleFinder( new BuildRuleResolver( TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()))); Archiver archiver = platform.getAr(); Path output = filesystem.getPath("output.a"); Path input = filesystem.getPath("foo/blah.dat"); filesystem.mkdirs(input.getParent()); filesystem.writeContentsToPath("blah", input); // Build an archive step. ArchiveStep archiveStep = new ArchiveStep( filesystem, archiver.getEnvironment(sourcePathResolver), archiver.getCommandPrefix(sourcePathResolver), ImmutableList.of(), getArchiveOptions(false), output, ImmutableList.of(input.getParent()), archiver); // Execute the archive step and verify it ran successfully. ExecutionContext executionContext = TestExecutionContext.newInstance(); TestConsole console = (TestConsole) executionContext.getConsole(); int exitCode = archiveStep.execute(executionContext).getExitCode(); assertEquals("archive step failed: " + console.getTextWrittenToStdErr(), 0, exitCode); // Now read the archive entries and verify that the timestamp, UID, and GID fields are // zero'd out. try (ArArchiveInputStream stream = new ArArchiveInputStream(new FileInputStream(filesystem.resolve(output).toFile()))) { ArArchiveEntry entry = stream.getNextArEntry(); assertThat(entry.getName(), Matchers.equalTo("blah.dat")); } } @Test public void thinArchives() throws IOException, InterruptedException { assumeTrue(Platform.detect() == Platform.MACOS || Platform.detect() == Platform.LINUX); ProjectFilesystem filesystem = new ProjectFilesystem(tmp.getRoot()); CxxPlatform platform = CxxPlatformUtils.build(new CxxBuckConfig(FakeBuckConfig.builder().build())); assumeTrue(platform.getAr().supportsThinArchives()); // Build up the paths to various files the archive step will use. SourcePathResolver sourcePathResolver = new SourcePathResolver( new SourcePathRuleFinder( new BuildRuleResolver( TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer()))); Archiver archiver = platform.getAr(); Path output = filesystem.getPath("foo/libthin.a"); filesystem.mkdirs(output.getParent()); // Create a really large input file so it's obvious that the archive is thin. Path input = filesystem.getPath("bar/blah.dat"); filesystem.mkdirs(input.getParent()); byte[] largeInputFile = new byte[1024 * 1024]; byte[] fillerToRepeat = "hello\n".getBytes(StandardCharsets.UTF_8); for (int i = 0; i < largeInputFile.length; i++) { largeInputFile[i] = fillerToRepeat[i % fillerToRepeat.length]; } filesystem.writeBytesToPath(largeInputFile, input); // Build an archive step. ArchiveStep archiveStep = new ArchiveStep( filesystem, archiver.getEnvironment(sourcePathResolver), archiver.getCommandPrefix(sourcePathResolver), ImmutableList.of(), getArchiveOptions(true), output, ImmutableList.of(input), archiver); // Execute the archive step and verify it ran successfully. ExecutionContext executionContext = TestExecutionContext.newInstance(); TestConsole console = (TestConsole) executionContext.getConsole(); int exitCode = archiveStep.execute(executionContext).getExitCode(); assertEquals("archive step failed: " + console.getTextWrittenToStdErr(), 0, exitCode); // Verify that the thin header is present. assertThat(filesystem.readFirstLine(output), Matchers.equalTo(Optional.of("!<thin>"))); // Verify that even though the archived contents is really big, the archive is still small. assertThat(filesystem.getFileSize(output), Matchers.lessThan(1000L)); // NOTE: Replace the thin header with a normal header just so the commons compress parser // can parse the archive contents. try (OutputStream outputStream = Files.newOutputStream(filesystem.resolve(output), StandardOpenOption.WRITE)) { outputStream.write(ObjectFileScrubbers.GLOBAL_HEADER); } // Now read the archive entries and verify that the timestamp, UID, and GID fields are // zero'd out. try (ArArchiveInputStream stream = new ArArchiveInputStream(new FileInputStream(filesystem.resolve(output).toFile()))) { ArArchiveEntry entry = stream.getNextArEntry(); // Verify that the input names are relative paths from the outputs parent dir. assertThat( entry.getName(), Matchers.equalTo(output.getParent().relativize(input).toString())); } } private static ImmutableList<String> getArchiveOptions(boolean isThinArchive) { String options = isThinArchive ? "qcT" : "qc"; return ImmutableList.of(options); } }