Skip to main content

willbe/tool/
cargo.rs

1/// Define a private namespace for all its items.
2#[ allow( clippy ::std_instead_of_alloc, clippy ::std_instead_of_core ) ]
3mod private
4{
5
6  #[ allow( unused_imports, clippy ::wildcard_imports ) ]
7  use crate ::tool :: *;
8
9  use std ::ffi ::OsString;
10  use std ::path ::PathBuf;
11  // use error ::err;
12  // use error ::untyped ::format_err;
13  use former ::Former;
14  use process_tools ::process;
15  // use process_tools ::process :: *;
16  // qqq: for Bohdan: bad
17  // use error ::Result;
18  // qqq: group dependencies
19
20  // qqq: for Bohdan: bad: tools can't depend on entitties!
21  use crate ::channel ::Channel;
22  // Explicit import for Result and its variants for pattern matching
23  use std ::result ::Result :: { Ok, Err };
24
25  // aaa: documentation /// aaa: documented
26
27  /// Represents options for packaging a project.
28  ///
29  /// The `PackOptions` struct encapsulates various options that can be configured when packaging a project,
30  /// including the path to the project, the distribution channel, and various flags for controlling the behavior of the packaging process.
31  #[ derive( Debug, Former, Clone ) ]
32  #[ allow( clippy ::struct_excessive_bools ) ]
33  pub struct PackOptions
34  {
35  /// The path to the project to be packaged.
36  ///
37  /// This field specifies the file system path where the project is located.
38  pub( crate ) path: PathBuf,
39  /// The distribution channel for the packaging project.
40  ///
41  /// This field specifies the channel through which the packaged project will be distributed.
42  ///
43  pub( crate ) channel: Channel,
44  /// Flag indicating whether to allow packaging even if the working directory is dirty.
45  ///
46  /// This field is set to `true` by default, meaning that packaging will proceed even if there are uncommitted changes.
47  #[ former( default = true ) ]
48  pub( crate ) allow_dirty: bool,
49  // qqq: rename to checking_changes
50  /// Flag indicating whether to skip verification checks.
51  #[ former( default = false ) ]
52  // aaa: don't abuse negative form, rename to checking_consistency
53  // renamed and changed logic
54  pub( crate ) checking_consistency: bool,
55  
56  /// An optional temporary path to be used during packaging.
57  ///
58  /// This field may contain a path to a temporary directory that will be used during the packaging process.
59  pub( crate ) temp_path: Option< PathBuf >,
60  /// Flag indicating whether to perform a dry run.
61  ///
62  /// This field specifies whether the packaging process should be a dry run, meaning that no actual changes will be made.
63  pub( crate ) dry: bool,
64 }
65
66  impl PackOptionsFormer
67  {
68  pub fn option_temp_path( mut self, value: impl Into< Option< PathBuf > > ) -> Self
69  {
70   self.storage.temp_path = value.into();
71   self
72 }
73 }
74
75  impl PackOptions
76  {
77  #[ allow( clippy ::if_not_else ) ]
78  fn to_pack_args( &self, toolchain: &str ) -> Vec< String >
79  {
80   // Building the full path to Cargo.toml
81   let manifest_path = self.path.join( "Cargo.toml" );
82   let normalized_manifest_path = manifest_path.to_string_lossy().replace( '\\', "/" );
83   [ "run".to_string(), toolchain.to_string(), "cargo".into(), "package".into() ]
84   .into_iter()
85   // clearly show the way to the manifesto
86   .chain( Some( "--manifest-path".to_string() ) )
87   .chain( Some( normalized_manifest_path ) )
88   .chain( if self.allow_dirty { Some( "--allow-dirty".to_string() ) } else { None } )
89   .chain( if !self.checking_consistency { Some( "--no-verify".to_string() ) } else { None } )
90   .chain( self.temp_path.clone().map( | p | vec![ "--target-dir".to_string(), p.to_string_lossy().into() ] ).into_iter().flatten() )
91   .collect()
92 }
93 }
94
95
96  ///
97  /// Assemble the local package into a distributable tarball.
98  ///
99  /// # Args :
100  /// - `path` - path to the package directory
101  /// - `dry` - a flag that indicates whether to execute the command or not
102  ///
103  // FIX: Added # Errors section for `pack` function
104  /// # Errors
105  ///
106  /// Returns an error if the `rustup ... cargo package` command fails.
107  ///
108  #[ cfg_attr
109  (
110  feature = "tracing",
111  track_caller,
112  tracing ::instrument( fields( caller = ?{ let x = std ::panic ::Location ::caller(); ( x.file(), x.line() ) } ) )
113 )]
114  // qqq: should be typed error, apply err_with
115  // qqq: use typed error
116  pub fn pack( args: PackOptions ) -> error ::untyped ::Result< process ::Report >
117  {
118  // Fix(issue-NNN): Resolve actual rustup toolchain identifier before invoking `rustup run`.
119  // Root cause: "rustup run stable" requires the "stable" alias; version-pinned toolchains
120  // (e.g. "1.94.1-aarch64-unknown-linux-gnu") do not register that alias.
121  // Pitfall: Channel::Stable formats as "stable" but that string only works when the alias exists.
122  let toolchain = crate ::channel ::toolchain_name( args.channel, &args.path )?;
123  let ( program, options ) = ( "rustup", args.to_pack_args( &toolchain ) );
124
125  if args.dry
126  {
127   Ok
128   (
129  process ::Report
130  {
131   command: format!( "{program} {}", options.join( " " ) ),
132   out: String ::new(),
133   err: String ::new(),
134   current_path: args.path.clone(),
135   error: Ok( () ),
136 }
137 )
138 }
139  else
140  {
141   process ::Run ::former()
142   .bin_path( program )
143   .args( options.into_iter().map( OsString ::from ).collect :: < Vec< _ > >() )
144   .current_path( args.path )
145   .run().map_err( | report | error ::untyped ::format_err!( report.to_string() ) )
146 }
147 }
148
149
150  /// Represents the options for the publish.
151  #[ derive( Debug, Former, Clone, Default ) ]
152  pub struct PublishOptions
153  {
154  pub( crate ) path: PathBuf,
155  pub( crate ) temp_path: Option< PathBuf >,
156  #[ former( default = 0usize ) ]
157  pub( crate ) retry_count: usize,
158  pub( crate ) dry: bool,
159 }
160
161  impl PublishOptionsFormer
162  {
163  pub fn option_temp_path( mut self, value: impl Into< Option< PathBuf > > ) -> Self
164  {
165   self.storage.temp_path = value.into();
166   self
167 }
168 }
169
170  impl PublishOptions
171  {
172  fn as_publish_args( &self ) -> Vec< String >
173  {
174   let target_dir = self.temp_path.clone().map( | p | vec![ "--target-dir".to_string(), p.to_string_lossy().into() ] );
175   [ "publish".to_string() ].into_iter().chain( target_dir.into_iter().flatten() ).collect()
176 }
177 }
178
179  /// Upload a package to the registry
180  // FIX: Added # Errors section for `publish` function
181  /// # Errors
182  ///
183  /// Returns an error if the `cargo publish` command fails after all retry attempts.
184  ///
185  #[ cfg_attr
186  (
187  feature = "tracing",
188  track_caller,
189  tracing ::instrument( fields( caller = ?{ let x = std ::panic ::Location ::caller(); ( x.file(), x.line() ) } ) )
190 )]
191  pub fn publish( args: &PublishOptions ) -> error ::untyped ::Result< process ::Report >
192  // qqq: use typed error
193  {
194  
195  let ( program, arguments) = ( "cargo", args.as_publish_args() );
196
197  if args.dry
198  {
199   Ok
200  (
201   process ::Report
202   {
203  command: format!( "{program} {}", arguments.join( " " ) ),
204  out: String ::new(),
205  err: String ::new(),
206  current_path: args.path.clone(),
207  error: Ok( () ),
208 }
209 )
210 }
211  else
212  {
213   let mut results = Vec ::with_capacity( args.retry_count + 1 );
214   let run_args: Vec< _ > =  arguments.into_iter().map( OsString ::from ).collect();
215   for _ in 0 ..=args.retry_count
216   {
217  let result = process ::Run ::former()
218  .bin_path( program )
219  .args( run_args.clone() )
220  .current_path( &args.path )
221  .run();
222  match result
223  {
224   Ok( report ) => return Ok( report ),
225   Err( e ) => results.push( e ),
226 }
227 }
228   if args.retry_count > 0
229   {
230  Err( error ::untyped ::format_err!
231  ( 
232   "It took {} attempts, but still failed. Here are the errors: \n{}", 
233   args.retry_count + 1, 
234   results
235   .into_iter()
236   .map( | r | format!( "- {r}" ) )
237   .collect :: < Vec< _ > >()
238   .join( "\n" ) 
239 ))
240 }
241   else
242   {
243  Err( results.remove( 0 ) ).map_err( | report | error ::untyped ::format_err!( report.to_string() ) )
244 }
245 }
246 }
247}
248
249//
250
251crate ::mod_interface!
252{
253  own use pack;
254  own use publish;
255
256  own use PublishOptions;
257  own use PackOptions;
258
259}