]> git.proxmox.com Git - dh-cargo.git/blob - cargo.pm
Don't assume library packages have arch:all
[dh-cargo.git] / cargo.pm
1 # debhelper buildsystem for Rust crates using Cargo
2 #
3 # Josh Triplett <josh@joshtriplett.org>
4
5 package Debian::Debhelper::Buildsystem::cargo;
6
7 use strict;
8 use warnings;
9 use Cwd;
10 use Debian::Debhelper::Dh_Lib;
11 use Dpkg::Changelog::Debian;
12 use Dpkg::Control::Info;
13 use Dpkg::Version;
14 use JSON::PP;
15 use base 'Debian::Debhelper::Buildsystem';
16
17 sub DESCRIPTION {
18 "Rust Cargo"
19 }
20
21 sub cargo_version {
22 my $src = shift;
23 open(F, "cargo metadata --manifest-path $src --no-deps --format-version 1 |");
24 local $/;
25 my $json = JSON::PP->new;
26 my $manifest = $json->decode(<F>);
27 return %{@{%{$manifest}{'packages'}}[0]}{'version'} . "";
28 }
29
30 sub deb_host_rust_type {
31 open(F, 'printf "include /usr/share/rustc/architecture.mk\n\
32 all:\n\
33 echo \$(DEB_HOST_RUST_TYPE)\n\
34 " | make --no-print-directory -sf - |');
35 $_ = <F>;
36 chomp;
37 return $_;
38 }
39
40 sub check_auto_buildable {
41 my $this = shift;
42 if (-f $this->get_sourcepath("Cargo.toml")) {
43 return 1;
44 }
45 return 0;
46 }
47
48 sub new {
49 my $class = shift;
50 my $this = $class->SUPER::new(@_);
51 $this->enforce_in_source_building();
52 return $this;
53 }
54
55 sub pre_building_step {
56 my $this = shift;
57 my $step = shift;
58
59 $this->{cargo_home} = Cwd::abs_path($this->get_sourcepath("debian/cargo_home"));
60 $this->{cargo_registry} = Cwd::abs_path($this->get_sourcepath("debian/cargo_registry"));
61
62 my $control = Dpkg::Control::Info->new();
63
64 my $source = $control->get_source();
65 my $crate = $source->{'X-Cargo-Crate'};
66 if (!$crate) {
67 $crate = $source->{Source};
68 $crate =~ s/^rust-//;
69 $crate =~ s/-[0-9]+(\.[0-9]+)*$//;
70 }
71 $this->{crate} = $crate;
72 my $changelog = Dpkg::Changelog::Debian->new(range => { count => 1 });
73 $changelog->load($this->get_sourcepath("debian/changelog"));
74 $this->{version} = Dpkg::Version->new(@{$changelog}[0]->get_version())->version();
75
76 my @packages = $control->get_packages();
77 $this->{libpkg} = 0;
78 $this->{binpkg} = 0;
79 $this->{featurepkg} = [];
80 foreach my $package (@packages) {
81 if ($package->{Package} =~ /^librust-.*-dev$/) {
82 if ($package->{Package} =~ /\+/) {
83 push(@{$this->{featurepkg}}, $package->{Package});
84 next;
85 }
86 if ($this->{libpkg}) {
87 error("Multiple Cargo lib packages found: " . $this->{libpkg} . " and " . $package->{Package});
88 }
89 $this->{libpkg} = $package->{Package};
90 } elsif ($package->{Architecture} ne 'all') {
91 $this->{binpkg} = $package->{Package};
92 }
93 }
94 if (!$this->{libpkg} && !$this->{binpkg}) {
95 error("Could not find any Cargo lib or bin packages to build.");
96 }
97 if (@{$this->{featurepkg}} && !$this->{libpkg}) {
98 error("Found feature packages but no lib package.");
99 }
100
101 my $parallel = $this->get_parallel();
102 $this->{j} = $parallel > 0 ? ["-j$parallel"] : [];
103
104 $this->SUPER::pre_building_step($step);
105 }
106
107 sub get_sources {
108 my $this=shift;
109 opendir(my $dirhandle, $this->get_sourcedir());
110 my @sources = grep { $_ ne '.' && $_ ne '..' && $_ ne '.git' && $_ ne 'debian' } readdir($dirhandle);
111 closedir($dirhandle);
112 @sources
113 }
114
115 sub configure {
116 my $this=shift;
117 # Create a fake local registry $this->{cargo_registry} with only our dependencies
118 my $crate = $this->{crate} . '-' . $this->{version};
119 my $registry = $this->{cargo_registry};
120 doit("mkdir", "-p", $this->{cargo_home}, $registry);
121 opendir(my $dirhandle, '/usr/share/cargo/registry');
122 my @crates = map { "/usr/share/cargo/registry/$_" } grep { $_ ne '.' && $_ ne '..' } readdir($dirhandle);
123 closedir($dirhandle);
124 if (@crates) {
125 doit("ln", "-st", "$registry", @crates);
126 }
127 # Handle the case of building the package with the same version of the
128 # package installed.
129 if (-l "$registry/$crate") {
130 unlink("$registry/$crate");
131 }
132 mkdir("$registry/$crate");
133 my @sources = $this->get_sources();
134 doit("cp", "-at", "$registry/$crate", @sources);
135 doit("cp", $this->get_sourcepath("debian/cargo-checksum.json"), "$registry/$crate/.cargo-checksum.json");
136
137 my @ldflags = split / /, $ENV{'LDFLAGS'};
138 @ldflags = map { "\"-C\", \"link-arg=$_\"" } @ldflags;
139 # We manually supply the linker to support cross-compilation
140 # This is because of https://github.com/rust-lang/cargo/issues/4133
141 my $rustflags_toml = join(", ",
142 '"-C"', '"linker=' . dpkg_architecture_value("DEB_HOST_GNU_TYPE") . '-gcc"',
143 '"-C"', '"debuginfo=2"',
144 @ldflags);
145 open(CONFIG, ">" . $this->{cargo_home} . "/config");
146 print(CONFIG qq{
147 [source.crates-io]
148 replace-with = "dh-cargo-registry"
149
150 [source.dh-cargo-registry]
151 directory = "$registry"
152
153 [build]
154 rustflags = [$rustflags_toml]
155 });
156 close(CONFIG);
157 }
158
159 sub test {
160 my $this=shift;
161 $ENV{'CARGO_HOME'} = $this->{cargo_home};
162 # Check that the thing compiles. This might fail if e.g. the package
163 # requires non-rust system dependencies and the maintainer didn't provide
164 # this additional information to debcargo.
165 doit("cargo", "build", "--verbose", @{$this->{j}},
166 "--target", deb_host_rust_type,
167 "-Zavoid-dev-deps");
168 }
169
170 sub install {
171 my $this=shift;
172 $ENV{'CARGO_HOME'} = $this->{cargo_home};
173 my $crate = $this->{crate} . '-' . $this->{version};
174 if ($this->{libpkg}) {
175 my $target = $this->get_sourcepath("debian/" . $this->{libpkg} . "/usr/share/cargo/registry/$crate");
176 my @sources = $this->get_sources();
177 doit("mkdir", "-p", $target);
178 doit("cp", "-at", $target, @sources);
179 doit("rm", "-rf", "$target/target");
180 doit("cp", $this->get_sourcepath("debian/cargo-checksum.json"), "$target/.cargo-checksum.json");
181 }
182 foreach my $pkg (@{$this->{featurepkg}}) {
183 my $target = $this->get_sourcepath("debian/$pkg/usr/share/doc");
184 doit("mkdir", "-p", $target);
185 doit("ln", "-s", $this->{libpkg}, "$target/$pkg");
186 }
187 if ($this->{binpkg}) {
188 my $target = $this->get_sourcepath("debian/" . $this->{binpkg} . "/usr");
189 doit("cargo", "install", "--verbose", @{$this->{j}},
190 "--target", deb_host_rust_type,
191 $this->{crate},
192 "--vers", cargo_version($this->get_sourcepath("Cargo.toml")),
193 "--root", $target);
194 doit("rm", "$target/.crates.toml");
195 }
196 }
197
198 sub clean {
199 my $this=shift;
200 $ENV{'CARGO_HOME'} = $this->{cargo_home};
201 doit("cargo", "clean", "--verbose");
202 doit("rm", "-rf", $this->{cargo_home}, $this->{cargo_registry});
203 }
204
205 1