Skip to content

Commit 38b664e

Browse files
authored
[Auditor] Add a pass to normalise timestamps in Windows import libraries (#1259)
* [Auditor] Add a pass to normalise timestamps in Windows import libraries This should ensure reproducibility of import libraries. * Run repro tests for Windows libraries on i686 This avoids downloading yet another compiler shard. * Force `Pkg.gc`
1 parent 582c274 commit 38b664e

File tree

4 files changed

+82
-2
lines changed

4 files changed

+82
-2
lines changed

azure-pipelines.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ jobs:
6363
steps:
6464
- bash: |
6565
set -e
66+
$(JULIA) -e 'using Pkg; Pkg.gc()'
6667
$(JULIA) --check-bounds=yes --inline=yes -e 'using Pkg; Pkg.Registry.update(); Pkg.instantiate(); Pkg.test(coverage=true)'
6768
$(JULIA) -e 'using Pkg; Pkg.add("Coverage"); using Coverage; Codecov.submit(process_folder())' || true
6869
name: Test

src/Auditor.jl

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,9 @@ function audit(prefix::Prefix, src_name::AbstractString = "";
201201
continue
202202
end
203203
# remove it
204-
@info("Removing libtool file $f")
204+
if verbose
205+
@info("Removing libtool file $f")
206+
end
205207
rm(f; force=true)
206208
end
207209

@@ -245,6 +247,16 @@ function audit(prefix::Prefix, src_name::AbstractString = "";
245247
mv(f, joinpath(prefix, "bin", basename(f)))
246248
end
247249
end
250+
251+
# Normalise timestamp of Windows import libraries.
252+
import_libraries = collect_files(prefix, endswith(".dll.a"))
253+
for implib in import_libraries
254+
if verbose
255+
@info("Normalising timestamps in import library $(implib)")
256+
end
257+
normalise_implib_timestamp(implib)
258+
end
259+
248260
end
249261

250262
# Check that we're providing a license file

src/auditor/extra_checks.jl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# This file contains some extra checks that don't fall into the other
22
# categories, for example because they're very platform-specific.
33

4+
using Dates: DateTime, datetime2unix
5+
46
function check_os_abi(oh::ObjectHandle, p::AbstractPlatform, rest...; verbose::Bool = false, kwargs...)
57
if Sys.isfreebsd(p)
68
if oh.ei.osabi != ELF.ELFOSABI_FREEBSD
@@ -29,3 +31,19 @@ function check_os_abi(oh::ObjectHandle, p::AbstractPlatform, rest...; verbose::B
2931
end
3032
return true
3133
end
34+
35+
# Problem: import libraries on Windows embed information about temporary object files,
36+
# including their modification time on disk, which makes import libraries non-reproducible:
37+
# <https://github.com/JuliaPackaging/BinaryBuilder.jl/issues/1245>.
38+
function normalise_implib_timestamp(path::AbstractString)
39+
# Format of the object file info is something like
40+
# / 1674301251 0 0 644 286 `
41+
# where `1674301251` is the Unix timestamp of the modification time of the library, we
42+
# normalise it to another timestamp. NOTE: it appears that changing the timestamp width
43+
# would break the library, so we use another fixed 10-digit wide timestamp. 10-digit
44+
# Unix timestamps span between 2001 and 2286, so we should be good for a while.
45+
timestamp = trunc(Int, datetime2unix(DateTime(2013, 2, 13, 0, 49, 0))) # Easter egg
46+
newlib = replace(read(path, String), r"(?<HEAD>/ +)(\d{10})(?<TAIL> +\d+ +\d+ +\d+ +\d+ +`)" => SubstitutionString("\\g<HEAD>$(timestamp)\\g<TAIL>"))
47+
# Write back the file to disk.
48+
write(path, newlib)
49+
end

test/auditing.jl

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,8 @@ end
306306
# The products we expect to be build
307307
libfoo_products,
308308
# No dependencies
309-
Dependency[]
309+
Dependency[];
310+
verbose = true,
310311
)
311312
end
312313

@@ -726,6 +727,54 @@ end
726727
end
727728
end
728729

730+
# References:
731+
# * https://github.com/JuliaPackaging/BinaryBuilder.jl/issues/1232
732+
# * https://github.com/JuliaPackaging/BinaryBuilder.jl/issues/1245
733+
@testset "Auditor - Reproducible libraries on Windows" begin
734+
platform = Platform("i686", "windows")
735+
expected_git_shas = Dict(
736+
v"4" => Base.SHA1("64017544630a472081ba10879e58901d5e3b53a0"),
737+
v"6" => Base.SHA1("31afae67c528151860c18128696b19c85309b556"),
738+
)
739+
@testset "gcc version $(gcc_version)" for gcc_version in (v"4", v"6")
740+
mktempdir() do build_path
741+
build_output_meta = nothing
742+
product = LibraryProduct("libfoo", :libfoo)
743+
@test_logs (:info, r"Normalising timestamps in import library") match_mode=:any begin
744+
build_output_meta = autobuild(
745+
build_path,
746+
"implib",
747+
v"1.0.0",
748+
# No sources
749+
FileSource[],
750+
# Build a library without execution permissions
751+
raw"""
752+
mkdir -p "${libdir}"
753+
cd "${libdir}"
754+
echo 'int foo(){ return 42; }' | cc -x c -shared - -o libfoo.${dlext} -Wl,--out-implib,libfoo.${dlext}.a
755+
""",
756+
# Build for Windows
757+
[platform],
758+
# Ensure our library product is built
759+
[product],
760+
# No dependencies
761+
Dependency[];
762+
verbose = true,
763+
require_license = false,
764+
preferred_gcc_version = gcc_version,
765+
)
766+
end
767+
768+
# Extract our platform's build
769+
@test haskey(build_output_meta, platform)
770+
tarball_path, tarball_hash = build_output_meta[platform][1:2]
771+
@test isfile(tarball_path)
772+
# Ensure reproducibility of build
773+
@test build_output_meta[platform][3] == expected_git_shas[gcc_version]
774+
end
775+
end
776+
end
777+
729778
@testset "Auditor - other checks" begin
730779
platform = Platform("armv7l", "linux"; call_abi = "eabihf", libc = "glibc")
731780
mktempdir() do build_path

0 commit comments

Comments
 (0)